package com.atguigu.tingshu.search.service.impl;

import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.Query;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import com.atguigu.tingshu.album.client.AlbumInfoFeignClient;
import com.atguigu.tingshu.album.client.CategoryFeignClient;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.model.album.AlbumAttributeValue;
import com.atguigu.tingshu.model.album.AlbumInfo;
import com.atguigu.tingshu.model.album.BaseCategoryView;
import com.atguigu.tingshu.model.search.AlbumInfoIndex;
import com.atguigu.tingshu.model.search.AttributeValueIndex;
import com.atguigu.tingshu.query.search.AlbumIndexQuery;
import com.atguigu.tingshu.search.repository.AlbumIndexRepository;
import com.atguigu.tingshu.search.service.SearchService;
import com.atguigu.tingshu.user.client.UserInfoFeignClient;
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;
import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
import com.atguigu.tingshu.vo.user.UserInfoVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.List;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;


@Slf4j
@Service
@SuppressWarnings({"all"})
public class SearchServiceImpl implements SearchService {

    @Autowired
    private AlbumInfoFeignClient albumInfoFeignClient;
    @Autowired
    private CategoryFeignClient categoryFeignClient;
    @Autowired
    private UserInfoFeignClient userInfoFeignClient;
    //内嵌了许多api方法，同时在启动时还会根据实体类的属性注解自动生成mapping结构，索引库！
    @Autowired
    private AlbumIndexRepository albumIndexRepository;
    @Autowired
    private ThreadPoolExecutor threadPoolExecutor;
    @Autowired
    private ElasticsearchClient elasticsearchClient;

    @Override
    public void upperAlbum(Long albumId) {
        //创建对象
        AlbumInfoIndex albumInfoIndex = new AlbumInfoIndex();
        //获取专辑信息
        CompletableFuture<AlbumInfo> albumInfoFuture = CompletableFuture.supplyAsync(() -> {
            Result<AlbumInfo> infoResult = albumInfoFeignClient.getAlbumInfo(albumId);
            //获取数据
            Assert.notNull(infoResult, "专辑信息结果集为空");
            AlbumInfo albumInfo = infoResult.getData();
            Assert.notNull(albumInfo, "专辑信息为空");
            //数据拷贝
            BeanUtils.copyProperties(albumInfo, albumInfoIndex);
            return albumInfo;
        }, threadPoolExecutor);

        //获取分类信息
        CompletableFuture<Void> categoryCompletableFuture = albumInfoFuture.thenAcceptAsync(albumInfo -> {
            Result<BaseCategoryView> baseCategoryViewResult = categoryFeignClient.getCategoryView(albumInfo.getCategory3Id());
            //获取数据
            Assert.notNull(baseCategoryViewResult, "分类信息结果集为空");
            BaseCategoryView baseCategoryView = baseCategoryViewResult.getData();
            Assert.notNull(baseCategoryView, "分类信息为空");
            albumInfoIndex.setCategory1Id(baseCategoryView.getCategory1Id());
            albumInfoIndex.setCategory2Id(baseCategoryView.getCategory2Id());
        }, threadPoolExecutor);

        //获取用户信息
        CompletableFuture<Void> userCompletableFuture = albumInfoFuture.thenAcceptAsync(albumInfo -> {
            Result<UserInfoVo> userInfoVoResult = userInfoFeignClient.getUserInfoVo(albumInfo.getUserId());
            Assert.notNull(userInfoVoResult, "用户信息结果集为空");
            UserInfoVo userInfoVo = userInfoVoResult.getData();
            Assert.notNull(userInfoVo, "用户信息为空");
            //设置数据
            albumInfoIndex.setAnnouncerName(userInfoVo.getNickname());
        }, threadPoolExecutor);


        //属性赋值
        CompletableFuture<Void> attrCompletableFuture = CompletableFuture.runAsync(() -> {
            Result<List<AlbumAttributeValue>> listResult = albumInfoFeignClient.findAlbumAttributeValue(albumId);
            Assert.notNull(listResult, "属性值结果集为空");
            List<AlbumAttributeValue> albumAttributeValueList = listResult.getData();
            Assert.notNull(albumAttributeValueList, "属性值为空");
            //循环遍历当年集合 获取attributeId 和 valueId 给 albumInfoIndex 中的属性赋值
            List<AttributeValueIndex> attributeValueIndexList = albumAttributeValueList.stream().map(albumAttributeValue -> {
                //创建对象
                AttributeValueIndex attributeValueIndex = new AttributeValueIndex();
                attributeValueIndex.setAttributeId(albumAttributeValue.getAttributeId());
                attributeValueIndex.setValueId(albumAttributeValue.getValueId());
                //返回数据
                return attributeValueIndex;
            }).collect(Collectors.toList());
            //进行赋值
            albumInfoIndex.setAttributeValueIndexList(attributeValueIndexList);
        }, threadPoolExecutor);

        //初始化专辑的统计数据
        albumInfoIndex.setPlayStatNum(new Random().nextInt(1000000));
        albumInfoIndex.setSubscribeStatNum(new Random().nextInt(1000000));
        albumInfoIndex.setBuyStatNum(new Random().nextInt(1000000));
        albumInfoIndex.setCommentStatNum(new Random().nextInt(1000000));

        //多任务组合
        CompletableFuture.allOf(
                categoryCompletableFuture,
                userCompletableFuture,
                albumInfoFuture,
                attrCompletableFuture
        ).join();
        log.info("上架专辑：{}", albumInfoIndex);
        //将这个数据放到es中
        albumIndexRepository.save(albumInfoIndex);
    }

    @Override
    public AlbumSearchResponseVo search(AlbumIndexQuery albumIndexQuery) {
        //1. 先生成dsl语句，构建一个 searchRequest对象
        SearchRequest searchRequest = this.queryBuilderDsl(albumIndexQuery);
        //2. 执行search方法
        SearchResponse<AlbumInfoIndex> searchResponse = null;
        try {
            searchResponse = elasticsearchClient.search(searchRequest, AlbumInfoIndex.class);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        //  3. 创建albumSearchResponseVo对象
        //  3.1 将searchResponseVo中的数据赋值给albumSearchResponseVo对象
        AlbumSearchResponseVo albumSearchResponseVo = this.parseResultDate(searchResponse);
        //  3.2 给albumSearchResponseVo属性赋值：
        //        private List<AlbumInfoIndexVo> list = new ArrayList<>();
        //        private Long total;//总记录数
        //        private Integer pageSize;//每页显示的内容
        //        private Integer pageNo;//当前页面
        //        private Long totalPages;
        //总记录数
        albumSearchResponseVo.setTotal(searchResponse.hits().total().value());
        //每页显示条数
        albumSearchResponseVo.setPageSize(albumIndexQuery.getPageSize());
        //当前页面
        albumSearchResponseVo.setPageNo(albumIndexQuery.getPageNo());
        //总页数
        albumSearchResponseVo.setTotalPages((albumSearchResponseVo.getTotal() + albumSearchResponseVo.getPageSize() - 1) / albumSearchResponseVo.getPageSize());
        //返回结果
        return albumSearchResponseVo;
    }

    private SearchRequest queryBuilderDsl(AlbumIndexQuery albumIndexQuery) {
        //创建 searchRequest 对象
        SearchRequest.Builder searchRequestBuilder = new SearchRequest.Builder();
        searchRequestBuilder.index("albuminfo");
        //创建一个query对象
        Query.Builder queryBuilder = new Query.Builder();
        // 创建一个bool对象
        BoolQuery.Builder boolQueryBuilder = new BoolQuery.Builder();
        //1. 判断用户是否根据关键词索引
        if (!StringUtils.isEmpty(albumIndexQuery.getKeyword())) {
            boolQueryBuilder.should(s -> s.match(m -> m.field("albumTitle").query(albumIndexQuery.getKeyword())))
                    .should(s -> s.match(m -> m.field("albumIntro").query(albumIndexQuery.getKeyword())))
                    .should(s -> s.match(m -> m.field("announcerName").query(albumIndexQuery.getKeyword())));

            //高亮
            searchRequestBuilder.highlight(h -> h.fields("albumTitle", hf -> hf.preTags("<span style='color:red'>").postTags("</span>")));
        }


        //2. 判断用户是否根据分类id查询
        if (null != albumIndexQuery.getCategory3Id()) {
            boolQueryBuilder.filter(f -> f.term(t -> t.field("category3Id").value(albumIndexQuery.getCategory3Id())));
        }
        if (null != albumIndexQuery.getCategory2Id()) {
            boolQueryBuilder.filter(f -> f.term(t -> t.field("category2Id").value(albumIndexQuery.getCategory2Id())));
        }
        if (null != albumIndexQuery.getCategory1Id()) {
            boolQueryBuilder.filter(f -> f.term(t -> t.field("category1Id").value(albumIndexQuery.getCategory1Id())));
        }
        //3. 判断用户是否根据属性查询 属性id 与 属性值id
        List<String> attributeList = albumIndexQuery.getAttributeList();
        //判断集合是否为空
        if (!CollectionUtils.isEmpty(attributeList)) {
            //循环遍历集合
            for (String attribute : attributeList) {
                //分割字符串
                String[] split = attribute.split(":");
                if (null != split && split.length == 2) {
                    boolQueryBuilder.filter(f -> f.nested(n -> n.path("attributeValueIndexList").query(q -> q.bool(b -> b
                            .filter(bf -> f.term(t -> t.field("attributeValueIndexList.attributeId").value(split[0])))
                            .filter(bf -> f.term(t -> t.field("attributeValueIndexList.valueId").value(split[1])))))));
                }
            }
        }

        //分页、排序
        int from = (albumIndexQuery.getPageNo() - 1) * albumIndexQuery.getPageSize();
        searchRequestBuilder.from(from);
        searchRequestBuilder.size(albumIndexQuery.getPageSize());

        //获取排序规则
        String order = albumIndexQuery.getOrder();
        if (!StringUtils.isEmpty(order)) {
            //order = 1:desc
            String[] split = order.split(":");
            if (null != split && split.length == 2) {
                String finalField = "";
                switch (split[0]) {
                    case "1":
                        finalField = "hotScore";
                        break;
                    case "2":
                        finalField = "playStatNum";
                        break;
                    case "3":
                        finalField = "createTime";
                }
                //设置排序规则
                String finalField1 = finalField;
                searchRequestBuilder.sort(s -> s.field(f -> f.field(finalField1).order("asc".equals(split[1])? SortOrder.Asc:SortOrder.Desc)));
            }
        }
        BoolQuery boolQuery = boolQueryBuilder.build();
        queryBuilder.bool(boolQuery);
        Query query = queryBuilder.build();
        //调用query方法
        searchRequestBuilder.query(query);
        //调用builder方法获取对象
        SearchRequest searchRequest = searchRequestBuilder.build();
        //打印dsl语句
        System.out.println("dsl:\t" + searchRequest.toString());
        //返回对象
        return searchRequest;
    }

    private AlbumSearchResponseVo parseResultDate(SearchResponse<AlbumInfoIndex> searchResponse) {
        //  主要给 private List<AlbumInfoIndexVo> list = new ArrayList<>(); 属性赋值;
        AlbumSearchResponseVo albumSearchResponseVo = new AlbumSearchResponseVo();
        //  获取数据
        List<Hit<AlbumInfoIndex>> hits = searchResponse.hits().hits();
        List<AlbumInfoIndexVo> list = hits.stream().map(hit -> {
            //  创建对象
            AlbumInfoIndexVo albumInfoIndexVo = new AlbumInfoIndexVo();
            //  获取到source
            AlbumInfoIndex albumInfoIndex = hit.source();
            //  赋值：
            BeanUtils.copyProperties(albumInfoIndex, albumInfoIndexVo);
            //  判断当前用户是否根据关键词进行检索，应该获取到高亮字段.
            if (null != hit.highlight().get("albumTitle")){
                //  获取高亮标题
                String albumTitle = hit.highlight().get("albumTitle").get(0);
                //  赋值
                albumInfoIndexVo.setAlbumTitle(albumTitle);
            }
            //  返回数据
            return albumInfoIndexVo;
        }).collect(Collectors.toList());
        //  赋值；
        albumSearchResponseVo.setList(list);
        //  返回数据
        return albumSearchResponseVo;
    }

    @Override
    public void lowerAlbum(Long albumId) {
        //删除数据
        albumIndexRepository.deleteById(albumId);
    }
}
